/*
 * Created on Feb 12, 2008
 */
package maxygen;

import com.cycling74.max.Atom;
import com.cycling74.max.MaxBox;
import com.cycling74.max.MaxObject;

/**
 * For dealing with inlets and outlet objects.
 * @author martinrobinson
 */
public class IOLet
{   
    /** Inlet an outlet types */
    public enum Type {
        /** Regular graphical inlet. */
        INLET,
        
        /** Regular graphical outlet. */
        OUTLET,
        
        /** Poly in object inlet. */
        POLYNORMALINLET,
        
        /** Poly out object outlet. */
        POLYNORMALOUTLET,
        
        /** Poly in~ object inlet. */
        POLYSIGNALINLET,
        
        /** Poly out object outlet. */
        POLYSIGNALOUTLET,
        
        /** Combined poly in and in~ object inlet. */
        POLYCOMBINEDINLET
    }
    
    Type type;
    int index; //(1, 2, 3, 4, 5, etc last - right inlet is negative i.e. -6)
    String comment;
    int x;
    int y;
    
    /**
     * Create a new inlet representation.
     * @param t     inlet type
     * @param i     inlet index
     * @param c     inlet comment/description/assistance text
     * @param x     horizontal position in the patcher
     * @param y     horizontal position in the patcher
     */
    IOLet(Type t, int i, String c, int x, int y)
    {
        type = t;
        index = i;
        comment = c;
        this.x = x;
        this.y = y;
    }
    
    /**
     * Create a new inlet representation.
     * @param t     inlet type
     * @param i     inlet index
     * @param c     inlet comment/description/assistance text
     */
    IOLet(Type t, int i, String c)
    {
        this(t, i, c, -1, -1);
    }
    
    /**
     * Get the text we need to search for in the binbuf for this type of inlet/outlet.
     * @param mode      type of inlet
     * @return          text to search for
     */ 
    public static String getModeComtag(Type mode)
    {
        switch(mode) {
        case INLET:
        case OUTLET:
            return "comlet";
        case POLYNORMALINLET:
        case POLYNORMALOUTLET:
        case POLYSIGNALINLET:
        case POLYSIGNALOUTLET:
        default:
            return "comment";
        }
    }
    
    /**
     * Get the inlet/outlet patcher coordinates from an array of Atoms depending
     * on the tpe of inlet/outlet.
     * @param list      Atoms obtained from the binbuf
     * @param mode      type of inlet/outlet
     * @return          coordinates as an int array
     */
    public static int[] getIOLetCoords(Atom list[], Type mode)
    {
        int length = list.length;
        
        switch(mode) {
        case INLET:
        case OUTLET:
            return new int[]{list[length-4].getInt(), list[length-3].getInt()};
        case POLYNORMALINLET:
        case POLYNORMALOUTLET:
        case POLYSIGNALINLET:
        case POLYSIGNALOUTLET:
        default:
            return new int[]{list[length-6].getInt(), list[length-5].getInt()};
        }
    }
    
    /**
     * Get the index from the binbuf for poly-type inlets. Normal inlets should return the
     * index argument.
     * @param list      Atoms obtained from the binbuf
     * @param mode      type of inlet/outlet
     * @param index     for normal inlets/outlets this is the index from the list of boxes
     *                  sorted by screen position
     * @return          index of the inlet/outlet
     */
    public static int getIOLetIndex(Atom list[], Type mode, int index)
    {
        int length = list.length;
        
        switch(mode) {
        case INLET:
        case OUTLET:
            return index;
        case POLYNORMALINLET:
        case POLYNORMALOUTLET:
        case POLYSIGNALINLET:
        case POLYSIGNALOUTLET:
        default:
            String indexString = list[length-1].getString();
            int stringLength = indexString.length();
            indexString = indexString.substring(0, stringLength-1);
            Atom item[] = Atom.parse(indexString);
                        
            return item.length == 0 ? -1 : item[0].getInt();
        }
    }
    
    /**
     * Determine if a box is an inlet/outlet box of a particualar type.
     * @param box       box to test
     * @param mode      type of inlet/outlet to search for
     * @return          whether the box is of this type
     */
    public static boolean isBoxIOlet(BoxWrapper box, Type mode)
    {   
        String name = box.getMaxClass();
        
        switch(mode) {
        case INLET:
            return name.equals("inlet");
        case OUTLET:
            return name.equals("outlet");
        case POLYNORMALINLET:
            return name.equals("in");
        case POLYNORMALOUTLET:
            return name.equals("out");
        case POLYSIGNALINLET:
            return name.equals("in~");
        case POLYSIGNALOUTLET:
            return name.equals("out~");
        default:
            return false;
        }  
    }
    
    /**
     * Generate a unique name for the inlet/outlet. This is used temporarily while obtaining
     * the inlet/outlet's binbuf. If the object is already named it is usually reset.
     * @param box       box to name
     * @param mode      type of inlet/outlet
     * @return          the unique name
     */
    public static String generateIOletName(BoxWrapper box, Type mode)
    {                
        switch(mode) {
        case INLET:
            return "inlet:"+StringUtility.generateNameString(box);
        case OUTLET:
            return "outlet:"+StringUtility.generateNameString(box);
        case POLYNORMALINLET:
            return "in:"+StringUtility.generateNameString(box);
        case POLYNORMALOUTLET:
            return "out:"+StringUtility.generateNameString(box);
        case POLYSIGNALINLET:
            return "in~:"+StringUtility.generateNameString(box);
        case POLYSIGNALOUTLET:
            return "out~:"+StringUtility.generateNameString(box);
        default:
            return StringUtility.generateNameString(box);
        }
    }
    
    /**
     * Determine if two inlets/outlets are already sorted in the correct order.
     * Both inlets/outlets should be the same type. These are sorted appropriately for 
     * the type of inlet (graphical types by screen position, poly types by index argument).
     * 
     * @param low       the inlet/outlet which should be in the low position
     * @param high      the inlet/outlet which should be in the high position
     * @return          whether the inlets/outlets are already sorted
     */
    public static boolean areSorted(IOLet low, IOLet high)
    {        
        //MaxObject.post("low="+low+" high="+high);
        
        Type mode = low.type;    // make this more intelligent, although currently 
                                //this method is used only when IOLets are the same type
        
        
        
        switch(mode) {
        case INLET:
        case OUTLET:
            // position sort
            if(low.x == high.x) {  
                /*
                 *  if two inlets are aligned vertically the inlet lower on the screen
                 *  will be to the left so in this case we consider > to be "lower"
                 */
                if(low.y >= high.y)
                    return true;
                else
                    return false;
                
            } else if(low.x <= high.x) {
                return true;
            } else {
                return false;
            }
        case POLYNORMALINLET:
        case POLYNORMALOUTLET:
        case POLYSIGNALINLET:
        case POLYSIGNALOUTLET:
        default:
            // index sort
            if(low.index <= high.index)
                return true;
            else
                return false;
        }
    }
    
    /**
     * Swap two inlets/outlets in an array
     * @param boxes     the inlet/outlet array
     * @param i         index of one of the inlets/outlets to swap
     * @param j         index of the other inlet/outlet to swap
     */
    public static void swap(IOLet iolets[], int i, int j)
    {
        IOLet tmp = iolets[i];
        iolets[i] = iolets[j];
        iolets[j] = tmp;
    }
    
    /**
     * Sort inlets/outlets.
     * @param boxes         the array of inlets/outlets to sort
     * @return              the sorted array of inlets/outlets
     */
    public static IOLet[] sort(IOLet iolets[])
    {
        /*
         * bubble sort shouldn't be too inefficient 
         * for the likely number of boxes in question
         */
        if(iolets.length < 2) return iolets;
        
        int numCompares = iolets.length-1;
        boolean swapped;
        
        do {
            swapped = false;
            for(int i = 0; i < numCompares; i++) {
                if(!IOLet.areSorted(iolets[i], iolets[i+1])) {
                    IOLet.swap(iolets, i, i+1);
                    swapped = true;
                }
            }
        } while(swapped);
        
        return iolets;
    }
    
    /**
     * This consolidates multiple inlets and outlets of the same type and index where appropriate.
     * E.g., if there are two "in 2" objects in a patch this will create only one inlet. Inlet/outlet
     * comments are also concatenated if the comments are different.
     * 
     * @param iolets        the array of inlets/outlets to consolidate
     * @return              the consolidated inlets/outlets
     */
    public static IOLet[] consolidate(IOLet iolets[])
    {
        if(iolets.length < 2) return iolets;
        
        iolets = sort(iolets);
        
        int countUnique = 1;
        int i;
        
        int prevIndex = iolets[0].index;
        for(i = 1; i < iolets.length; i++) {           
            int thisIndex = iolets[i].index;
            if(thisIndex != prevIndex)
                countUnique++;
            
            prevIndex = thisIndex;
        }
                
        IOLet newlets[] = new IOLet[countUnique];
        
        int j = prevIndex = -1;
        //prevIndex = iolets[0].index;
        //newlets[j] = new IOLet(iolets[0].type, iolets[0].index, iolets[0].comment);
        
        for(i = 0; i < iolets.length; i++) {
            int thisIndex = iolets[i].index;
            
            if(thisIndex == prevIndex) {
                String thisComment = StringUtility.modifyFirstTypeDesc(iolets[i].comment);
                
                if(!newlets[j].comment.equals(thisComment))
                    newlets[j].comment += "<p>\n\t\t"+thisComment;
            } else {
                newlets[++j] = new IOLet(iolets[i].type, thisIndex, iolets[i].comment);
            }
            
            prevIndex = thisIndex;
        }
        
        return newlets;
    }
}
